#!/usr/bin/lua
local function languageTest() 
	-- table test
	local names = {"Peter", "Paul", "Mary"}
	local grades = {Mary=10, Paul=7, Peter=8}
	table.sort(names, function(n1, n2) 
		return grades[n1] > grades[n2]
	end)
	for i=1, #names do
		print(names[i])
	end
	
	-- function test
	local function newCounter(name)
		local i = 0
		return function()
				i = i+1
				return name .. ":" .. i
			end
	end
	
	local c1 = newCounter("c1")
	local c2 = newCounter("c2")
	
	print(c1())
	print(c1())
	print(c2())
	print(c1())
	print(c2())
	
	-- for test
	local function values(t)
		local i = 0;
		return function() i=i+1; return t[i] end
	end
	for elm in values(names) do
		print(elm)
	end
	
--	-- for test2
--	for k in pairs(names) do
--		print(k)
--	end
end

local function tableTest() 
	local Set = {}
	local mt = {}
	
	-- create a new set with teh values of the given list
	Set.new = function(l)
		local set = {}
		setmetatable(set, mt)
		for _, v in ipairs(l) do set[v] = true end
		return set;
	end
	
	Set.union = function(a, b)
		if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
			error("attempt to 'add' a set with a non-set value", 2);
		end
		local res = Set.new {}
		for k in pairs(a) do res[k] = true end
		for k in pairs(b) do res[k] = true end
		return res
	end
	
	Set.intersection = function(a, b)
		local res = Set.new {}
		for k in pairs(a) do 
			res[k] = b[k]
		end
		return res
	end
	
	Set.tostring = function(set)
		local l = {}
		for e in pairs(set) do 
			l[#l+1] = e
		end
		return "{" .. table.concat(l, ", ") .. "}"
	end
	
	Set.print = function(s)
		print(Set.tostring(s))
	end
	
	mt.__add = Set.union
	mt.__mul = Set.intersection
	mt.__tostring = Set.tostring
	mt.__le = function(a, b)
		for k in pairs(a) do 
			if not b[k] then return false end
		end
		return true
	end
	mt.__lt = function(a, b)
		return a<=b and not (b<=a)
	end
	mt.__eq = function(a, b)
		return a<=b and b<=a
	end
	
	
	local s1 = Set.new {10, 20, 30, 50}
	local s2 = Set.new {30, 1}
	local s3 = s1+s2+s2
--	local s3 = s1+s2+s2 + 8
	Set.print(s3)
	Set.print((s1+s2)*s1)
	
	s1 = Set.new{2, 4}
	s2 = Set.new{4, 10, 2}
	print(s1<=s2)
	print(s1<s2)
	print(s1>=s2)
	print(s1>s2)
	print(s1==s2*s1)
	
	s1 = Set.new {10, 4, 5}
	print(s1)
	
--	mt.__metatable = "not your business"
--	print(getmetatable(s1))
--	setmetatable(s1, {})
	
	local Window = {} -- create a namespace
	--create teh prototype with default values.
	Window.prototype = {x=0, y=0, width=100, height=100}
	Window.mt = {} -- create a metatable
	--declare the constructor function
	function Window.new(o)
		setmetatable(o, Window.mt)
		return o
	end
	Window.mt.__index = function(table, key)
		return Window.prototype[key]
	end
--	Window.mt.__index = Window.prototype
	w = Window.new {x=10, y=20}
	print(w.x)
	
	
	-- Tables with default values
	local function setDefault(t, d)
		local mt = {__index = function() return d end}
		setmetatable(t, mt)
	end
	local tab = {x=10, y=20}
	print(tab.x, tab.z)
	setDefault(tab, 0)
	print(tab.x, tab.z)
	
	local mt = {__index = function(t) return t.___ end }
	local function setDefault(t, d)
		t.___ = d
		setmetatable(t, mt)
	end
	
	-- Tracking table accesses
	local t = {} -- original table (created somewhere)
	-- keep a private access to the original table
	local _t = t
	-- create proxy
	t = {}
	-- create metatable
	local mt = {
		__index = function(t, k)
			print("*access to element " .. tostring(k))
			return _t[k]
		end,
		
		__newindex = function(t, k, v)
			print("*update of element " .. tostring(k) .. " to " .. tostring(v))
			_t[k] = v -- update original table
		end
	}
	setmetatable(t, mt)
	t[2] = "hello"
	print(t[2])
	
	-- Read-only tables
	function readOnly(t)
		local proxy = {}
		local mt = {
			__index = t,
			__newindex = function(t, k, v)
				error("attempt to update a read-only table", 2)
			end
		}
		setmetatable(proxy, mt)
		return proxy
	end
	days = readOnly {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
	print(days[1]) 
--	days[2] = "Noday"
end

local function objectTest()
	local Account = {
		balance = 0,
		new = function(self, o)
			o = o or {} -- create table is user does not provide one
			setmetatable(o, self)
			self.__index = self
			return o
		end,
		withdraw = function(self, v)
			if v>self.balance then error"insufficient funds" end
			self.balance = self.balance - v
		end,
		deposit = function(self, v)
			self.balance = self.balance + v
		end
	}
	
	local a = Account:new {balance = 0}
	a:deposit(100.00)
	print(a.balance)
	local b = Account:new()
	print(b.balance)
	b.balance = 123
	print(b.balance)
	b:withdraw(100)
	print(b.balance)
	print(a.balance)
	
	
	-- inheritance
	local SpecialAccount = Account:new({
		withdraw = function(self, v)
			if v - self.balance >= self:getLimit() then
				error"insufficient funds"
			end
			self.balance = self.balance - v
		end, 
		getLimit = function(self) 
			return self.limit or 0
		end
	})
	
	local s = SpecialAccount:new {limit=1000.00}
	s:deposit(100.00)
	s:withdraw(200.00)
	print("s:", s.balance)
end


local function main()
	languageTest();
	tableTest();
	objectTest();
end
main()
